#include "os.h"

#include <arpa/inet.h>
#include <fcntl.h>
#include <stdbool.h>
#include <stdio.h>
#include <sys/socket.h>

#include "log.h"

/******************************************************************************/
/* network */
/******************************************************************************/

int set_nonblock(int fd)
{
    int flags = fcntl(fd, F_GETFL, 0);
    if (flags < 0)
        log_fatal("fcntl get\n");

    if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) < 0)
        log_fatal("fcntl set\n");

    return 0;
}

int server(char *addr, int port)
{
    int sock = 0;
    int opt;

    sock = socket(AF_INET, SOCK_STREAM, 0);
    if (sock == -1)
        log_fatal("server socket");

    opt = SO_REUSEADDR;
    setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));

    struct sockaddr_in saddr = { 0 };
    saddr.sin_family = AF_INET;
    saddr.sin_port = htons(port);
    saddr.sin_addr.s_addr = (addr == NULL) ? htonl(INADDR_ANY) : inet_addr(addr);

    if (bind(sock, (struct sockaddr *)&saddr, sizeof(saddr)) < 0)
        log_fatal("bind");

    if (listen(sock, 5) < 0)
        log_fatal("listen");

    return sock;
}

int client(char *addr, int port)
{
    int sock = 0;
    socklen_t socklen;

    sock = socket(AF_INET, SOCK_STREAM, 0);
    if (sock < 0)
        log_fatal("client sock\n");

    struct sockaddr_in caddr = { 0 };
    caddr.sin_family = AF_INET;
    caddr.sin_addr.s_addr = inet_addr(addr);
    caddr.sin_port = htons(port);

    socklen = sizeof(struct sockaddr_in);

    /* connect socket */
    if (connect(sock, (const struct sockaddr *)&caddr, socklen) < 0)
        log_error("connect\n");

    return sock;
}

int recv_line(int sock, char *buf, int size)
{
    int i = 0;
    char c = '\0';
    int n;

    while ((i < size - 1) && (c != '\n')) {
        n = recv(sock, &c, 1, 0);
        /* DEBUG printf("%02X\n", c); */
        if (n > 0) {
            if (c == '\r') {
                n = recv(sock, &c, 1, MSG_PEEK);
                /* DEBUG printf("%02X\n", c); */
                if ((n > 0) && (c == '\n'))
                    recv(sock, &c, 1, 0);
                else
                    c = '\n';
            }
            buf[i] = c;
            i++;
        } else
            c = '\n';
    }
    buf[i] = '\0';

    return (i);
}

int send_all(int sock, const void *buf, int len)
{
    int ret = 0;
    int off = 0;

redo:
    ret = send(sock, buf + off, len - off, 0);
    if (ret <= 0)
        return ret;

    off += ret;
    if (off < len)
        goto redo;

    return off;
}

void send_file(int sock, FILE *fp)
{
    char buf[4096];
    int len;

    len = fread(buf, sizeof(*buf), sizeof(buf), fp);
    while (!feof(fp)) {
        send_all(sock, buf, len);
        len = fread(buf, sizeof(*buf), sizeof(buf), fp);
    }
}

void get_ip_port(int sock, char *ip, unsigned short *port)
{
    struct sockaddr_in addr = { 0 };
    socklen_t socklen = sizeof(addr);
    if (getsockname(sock, (struct sockaddr *)&addr, &socklen) == -1)
        log_fatal("getsockname");

    if (inet_ntop(AF_INET, &addr.sin_addr, ip, INET_ADDRSTRLEN) == NULL)
        log_fatal("inet_ntop");

    *port = ntohs(addr.sin_port);
}

/******************************************************************************/
/* time */
/******************************************************************************/
